#include "apis.hpp"
#include "config.h"
#include "pch.hpp"
#include "sts.hpp"

void setup(void) {
  GIOChannel *gchanin;
  GdkDisplay *gdpy;
  int i, j;

  /* clean up any zombies immediately */
  sigchld(0);
  if (signal(SIGHUP, sighup) == SIG_ERR) {
    die("Can't install SIGHUP handler");
  }
  if (!(dpy = XOpenDisplay(NULL))) {
    die("Can't open default display");
  }

  /* atoms */
  atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False);
  atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False);
  atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False);
  atoms[AtomUTF8] = XInternAtom(dpy, "UTF8_STRING", False);

  gtk_init(NULL, NULL);

  gdpy = gdk_display_get_default();

  curconfig = defconfig;
  /* dirs and files */
  cookiefile = buildfile(cookiefile);
  scriptfile = buildfile(scriptfile);
  certdir = buildpath(certdir);
  if (curconfig[Ephemeral].val.i) {
    cachedir = NULL;
  } else {
    cachedir = buildpath(cachedir);
  }

  gdkkb = gdk_seat_get_keyboard(gdk_display_get_default_seat(gdpy));

  if (socketpair(AF_UNIX, SOCK_DGRAM, 0, spair) < 0) {
    fputs("Unable to create sockets\n", stderr);
    spair[0] = spair[1] = -1;
  } else {
    gchanin = g_io_channel_unix_new(spair[0]);
    g_io_channel_set_encoding(gchanin, NULL, NULL);
    g_io_channel_set_flags(
        gchanin,
        static_cast<GIOFlags>(g_io_channel_get_flags(gchanin) |
                              G_IO_FLAG_NONBLOCK),
        NULL);
    g_io_channel_set_close_on_unref(gchanin, TRUE);
  }

  for (i = 0; i < LENGTH(certs); ++i) {
    if (!regcomp(&(certs[i].re), certs[i].regex, REG_EXTENDED)) {
      certs[i].file = g_strconcat(certdir, "/", certs[i].file, NULL);
    } else {
      fprintf(stderr, "Could not compile regex: %s\n", certs[i].regex);
      certs[i].regex = NULL;
    }
  }

  if (!stylefile) {
    styledir = buildpath(styledir);
    for (i = 0; i < LENGTH(styles); ++i) {
      if (!regcomp(&(styles[i].re), styles[i].regex, REG_EXTENDED)) {
        styles[i].file = g_strconcat(styledir, "/", styles[i].file, NULL);
      } else {
        fprintf(stderr, "Could not compile regex: %s\n", styles[i].regex);
        styles[i].regex = NULL;
      }
    }
    g_free(styledir);
  } else {
    stylefile = buildfile(stylefile);
  }

  for (i = 0; i < LENGTH(uriparams); ++i) {
    if (regcomp(&(uriparams[i].re), uriparams[i].uri, REG_EXTENDED)) {
      fprintf(stderr, "Could not compile regex: %s\n", uriparams[i].uri);
      uriparams[i].uri = NULL;
      continue;
    }

    /* copy default parameters with higher priority */
    for (j = 0; j < ParameterLast; ++j) {
      if (defconfig[j].prio >= uriparams[i].config[j].prio) {
        uriparams[i].config[j] = defconfig[j];
      }
    }
  }
}

Client *newclient(Client *rc) {
  Client *c;

  if (!(c = (Client *)calloc(1, sizeof(Client)))) {
    die("Cannot malloc!\n");

    c->next = clients;
  }
  clients = c;

  c->progress = 100;
  c->view = newview(c, rc ? rc->view : NULL);

  return c;
}

WebKitWebView *newview(Client *c, WebKitWebView *rv) {
  WebKitWebView *v;
  WebKitSettings *settings;
  WebKitWebContext *context;
  WebKitCookieManager *cookiemanager;
  WebKitUserContentManager *contentmanager;

  /* Webview */
  if (rv) {
    v = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(rv));
    context = webkit_web_view_get_context(v);
    settings = webkit_web_view_get_settings(v);
  } else {
    settings = webkit_settings_new_with_settings(
        "allow-file-access-from-file-urls",
        curconfig[FileURLsCrossAccess].val.i,
        "allow-universal-access-from-file-urls",
        curconfig[FileURLsCrossAccess].val.i, "auto-load-images",
        curconfig[LoadImages].val.i, "default-charset",
        curconfig[DefaultCharset].val.v, "default-font-size",
        curconfig[FontSize].val.i, "enable-caret-browsing",
        curconfig[CaretBrowsing].val.i, "enable-developer-extras",
        curconfig[Inspector].val.i, "enable-dns-prefetching",
        curconfig[DNSPrefetch].val.i, "enable-html5-database",
        curconfig[DiskCache].val.i, "enable-html5-local-storage",
        curconfig[DiskCache].val.i, "enable-javascript",
        curconfig[JavaScript].val.i, "enable-site-specific-quirks",
        curconfig[SiteQuirks].val.i, "enable-smooth-scrolling",
        curconfig[SmoothScrolling].val.i, "enable-webgl",
        curconfig[WebGL].val.i, "media-playback-requires-user-gesture",
        curconfig[MediaManualPlay].val.i, NULL);
    /* For more interesting settings, have a look at
     * http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html
     */

    if (strcmp(fulluseragent, "")) {
      webkit_settings_set_user_agent(settings, fulluseragent);
    } else if (surfuseragent) {
      webkit_settings_set_user_agent_with_application_details(settings, "MiBr",
                                                              "0.0.1");
    }
    useragent = webkit_settings_get_user_agent(settings);

    contentmanager = webkit_user_content_manager_new();

    if (curconfig[Ephemeral].val.i) {
      context = webkit_web_context_new_ephemeral();
    } else {
      context = webkit_web_context_new_with_website_data_manager(
          webkit_website_data_manager_new("base-cache-directory", cachedir,
                                          "base-data-directory", cachedir,
                                          NULL));
    }

    cookiemanager = webkit_web_context_get_cookie_manager(context);

    /* TLS */
    webkit_website_data_manager_set_tls_errors_policy(
        webkit_web_context_get_website_data_manager(context),
        curconfig[StrictTLS].val.i ? WEBKIT_TLS_ERRORS_POLICY_FAIL
                                   : WEBKIT_TLS_ERRORS_POLICY_IGNORE);
    /* disk cache */
    webkit_web_context_set_cache_model(
        context, curconfig[DiskCache].val.i
                     ? WEBKIT_CACHE_MODEL_WEB_BROWSER
                     : WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);

    /* Currently only works with text file to be compatible with curl */
    if (!curconfig[Ephemeral].val.i) {
      webkit_cookie_manager_set_persistent_storage(
          cookiemanager, cookiefile, WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
    }
    /* cookie policy */
    webkit_cookie_manager_set_accept_policy(cookiemanager, cookiepolicy_get());
    /* languages */
    webkit_web_context_set_preferred_languages(
        context,
        static_cast<gchar const *const *>(curconfig[PreferredLanguages].val.v));
    webkit_web_context_set_spell_checking_languages(
        context,
        static_cast<gchar const *const *>(curconfig[SpellLanguages].val.v));
    webkit_web_context_set_spell_checking_enabled(
        context, static_cast<int>(curconfig[SpellChecking].val.i));

    g_signal_connect(G_OBJECT(context), "download-started",
                     G_CALLBACK(downloadstarted), c);
    g_signal_connect(G_OBJECT(context), "initialize-web-extensions",
                     G_CALLBACK(initwebextensions), c);

    v = static_cast<WebKitWebView *>(g_object_new(
        WEBKIT_TYPE_WEB_VIEW, "settings", settings, "user-content-manager",
        contentmanager, "web-context", context, NULL));
  }

  g_signal_connect(G_OBJECT(v), "notify::estimated-load-progress",
                   G_CALLBACK(progresschanged), c);
  g_signal_connect(G_OBJECT(v), "notify::title", G_CALLBACK(titlechanged), c);
  g_signal_connect(G_OBJECT(v), "button-release-event",
                   G_CALLBACK(buttonreleased), c);
  g_signal_connect(G_OBJECT(v), "close", G_CALLBACK(closeview), c);
  g_signal_connect(G_OBJECT(v), "create", G_CALLBACK(createview), c);
  g_signal_connect(G_OBJECT(v), "decide-policy", G_CALLBACK(decidepolicy), c);
  g_signal_connect(G_OBJECT(v), "insecure-content-detected",
                   G_CALLBACK(insecurecontent), c);
  g_signal_connect(G_OBJECT(v), "load-failed-with-tls-errors",
                   G_CALLBACK(loadfailedtls), c);
  g_signal_connect(G_OBJECT(v), "load-changed", G_CALLBACK(loadchanged), c);
  g_signal_connect(G_OBJECT(v), "mouse-target-changed",
                   G_CALLBACK(mousetargetchanged), c);
  g_signal_connect(G_OBJECT(v), "permission-request",
                   G_CALLBACK(permissionrequested), c);
  g_signal_connect(G_OBJECT(v), "ready-to-show", G_CALLBACK(showview), c);
  g_signal_connect(G_OBJECT(v), "user-message-received",
                   G_CALLBACK(viewusrmsgrcv), c);
  g_signal_connect(G_OBJECT(v), "web-process-terminated",
                   G_CALLBACK(webprocessterminated), c);

  c->context = context;
  c->settings = settings;

  setparameter(c, 0, DarkMode, &curconfig[DarkMode].val);
  return v;
}

void showview(WebKitWebView *v, Client *c) {}

void loaduri(Client *c, Arg const *a) {}

void updatetitle(Client *c) {}

void cleanup(void) {}

void die(char const *errstr, ...) {
  va_list ap;

  va_start(ap, errstr);
  vfprintf(stderr, errstr, ap);
  va_end(ap);
  exit(1);
}

void sigchld(int unused) {
  if (signal(SIGCHLD, sigchld) == SIG_ERR) {
    die("Can't install SIGCHLD handler");
  }
  while (waitpid(-1, NULL, WNOHANG) > 0)
    ;
}

void sighup(int unused) {
  Arg a = {.i = 0};
  Client *c;

  for (c = clients; c; c = c->next) {
    reload(c, &a);
  }
}

void reload(Client *c, Arg const *a) {
  if (a->i) {
    webkit_web_view_reload_bypass_cache(c->view);
  } else {
    webkit_web_view_reload(c->view);
  }
}

char *buildfile(char const *path) {
  char *dname, *bname, *bpath, *fpath;
  FILE *f;

  dname = g_path_get_dirname(path);
  bname = g_path_get_basename(path);

  return fpath;
}

char *buildpath(char const *path) {
  char *apath, *fpath;

  if (path[0] == '~') {
    apath = untildepath(path);
  } else {
    apath = g_strdup(path);
  }

  /* creating directory */
  if (g_mkdir_with_parents(apath, 0700) < 0) {
    die("Could not access directory: %s\n", apath);
  }

  fpath = realpath(apath, NULL);
  g_free(apath);

  return fpath;
}

char *untildepath(char const *path) {
  char *apath, *name, *p;
  char const *homedir;

  if (path[1] == '/' || path[1] == '\0') {
    p = (char *)&path[1];
    homedir = getcurrentuserhomedir();
  } else {
    // TODO:
    if ((p = strchr(const_cast<char *>(path), '/'))) {
      name = g_strndup(&path[1], p - (path + 1));
    } else {
      name = g_strdup(&path[1]);
    }

    homedir = getuserhomedir(name);
    g_free(name);
  }
  apath = g_build_filename(homedir, p, NULL);
  return apath;
}

char const *getcurrentuserhomedir(void) {
  char const *homedir;
  char const *user;
  struct passwd *pw;

  homedir = getenv("HOME");
  if (homedir) {
    return homedir;
  }

  user = getenv("USER");
  if (user) {
    return getuserhomedir(user);
  }

  pw = getpwuid(getuid());
  if (!pw) {
    die("Can't get current user home directory\n");
  }

  return pw->pw_dir;
}

char const *getuserhomedir(char const *user) {
  struct passwd *pw = getpwnam(user);

  if (!pw) {
    die("Can't get user %s login information.\n", user);
  }

  return pw->pw_dir;
}

WebKitCookieAcceptPolicy cookiepolicy_get(void) {
  switch (((char *)curconfig[CookiePolicies].val.v)[cookiepolicy]) {
  case 'a':
    return WEBKIT_COOKIE_POLICY_ACCEPT_NEVER;
  case '@':
    return WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY;
  default: /* fallthrough */
  case 'A':
    return WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS;
  }
}

void downloadstarted(WebKitWebContext *wc, WebKitDownload *d, Client *c) {
  g_signal_connect(G_OBJECT(d), "notify::response",
                   G_CALLBACK(responsereceived), c);
}

void initwebextensions(WebKitWebContext *wc, Client *c) {
  webkit_web_context_set_web_extensions_directory(wc, WEBEXTDIR);
}

void progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c) {
  c->progress = webkit_web_view_get_estimated_load_progress(c->view) * 100;
  updatetitle(c);
}

void titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c) {
  c->title = webkit_web_view_get_title(c->view);
  updatetitle(c);
}

gboolean buttonreleased(GtkWidget *w, GdkEvent *e, Client *c) {
  WebKitHitTestResultContext element;
  int i;

  element = static_cast<WebKitHitTestResultContext>(
      webkit_hit_test_result_get_context(c->mousepos));

  for (i = 0; i < LENGTH(buttons); ++i) {
    if (element & buttons[i].target && e->button.button == buttons[i].button &&
        CLEANMASK(e->button.state) == CLEANMASK(buttons[i].mask) &&
        buttons[i].func) {
      buttons[i].func(c, &buttons[i].arg, c->mousepos);
      return buttons[i].stopevent;
    }
  }

  return FALSE;
}

void closeview(WebKitWebView *v, Client *c) { gtk_widget_destroy(c->win); }

GtkWidget *createview(WebKitWebView *v, WebKitNavigationAction *a, Client *c) {
  Client *n;

  switch (webkit_navigation_action_get_navigation_type(a)) {
  case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
    /*
     * popup windows of type “other” are almost always triggered
     * by user gesture, so inverse the logic here
     */
    /* instead of this, compare destination uri to mouse-over uri for
     * validating window */
    if (webkit_navigation_action_is_user_gesture(a)) {
      return NULL;
    }
  case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:   /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:   /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_RELOAD:         /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
    n = newclient(c);
    break;
  default:
    return NULL;
  }

  return GTK_WIDGET(n->view);
}

gboolean decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
                      WebKitPolicyDecisionType dt, Client *c) {
  switch (dt) {
  case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
    decidenavigation(d, c);
    break;
  case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
    decidenewwindow(d, c);
    break;
  case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
    decideresource(d, c);
    break;
  default:
    webkit_policy_decision_ignore(d);
    break;
  }
  return TRUE;
}

void insecurecontent(WebKitWebView *v, WebKitInsecureContentEvent e,
                     Client *c) {
  c->insecure = 1;
}

gboolean loadfailedtls(WebKitWebView *v, gchar *uri, GTlsCertificate *cert,
                       GTlsCertificateFlags err, Client *c) {
  GString *errmsg = g_string_new(NULL);
  gchar *html, *pem;

  c->failedcert = g_object_ref(cert);
  c->tlserr = err;
  c->errorpage = 1;

  if (err & G_TLS_CERTIFICATE_UNKNOWN_CA) {
    g_string_append(errmsg,
                    "The signing certificate authority is not known.<br>");
  }
  if (err & G_TLS_CERTIFICATE_BAD_IDENTITY) {
    g_string_append(errmsg,
                    "The certificate does not match the expected identity "
                    "of the site that it was retrieved from.<br>");
  }
  if (err & G_TLS_CERTIFICATE_NOT_ACTIVATED) {
    g_string_append(errmsg, "The certificate's activation time "
                            "is still in the future.<br>");
  }
  if (err & G_TLS_CERTIFICATE_EXPIRED) {
    g_string_append(errmsg, "The certificate has expired.<br>");
  }
  if (err & G_TLS_CERTIFICATE_REVOKED) {
    g_string_append(errmsg,
                    "The certificate has been revoked according to "
                    "the GTlsConnection's certificate revocation list.<br>");
  }
  if (err & G_TLS_CERTIFICATE_INSECURE) {
    g_string_append(errmsg,
                    "The certificate's algorithm is considered insecure.<br>");
  }
  if (err & G_TLS_CERTIFICATE_GENERIC_ERROR) {
    g_string_append(errmsg,
                    "Some error occurred validating the certificate.<br>");
  }

  g_object_get(cert, "certificate-pem", &pem, NULL);
  html = g_strdup_printf("<p>Could not validate TLS for “%s”<br>%s</p>"
                         "<p>You can inspect the following certificate "
                         "with Ctrl-t (default keybinding).</p>"
                         "<p><pre>%s</pre></p>",
                         uri, errmsg->str, pem);
  g_free(pem);
  g_string_free(errmsg, TRUE);

  webkit_web_view_load_alternate_html(c->view, html, uri, NULL);
  g_free(html);

  return TRUE;
}

void loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c) {
  char const *uri = geturi(c);

  switch (e) {
  case WEBKIT_LOAD_STARTED:
    setatom(c, AtomUri, uri);
    c->title = uri;
    c->https = c->insecure = 0;
    seturiparameters(c, uri, loadtransient);
    if (c->errorpage) {
      c->errorpage = 0;
    } else {
      g_clear_object(&c->failedcert);
    }
    break;
  case WEBKIT_LOAD_REDIRECTED:
    setatom(c, AtomUri, uri);
    c->title = uri;
    seturiparameters(c, uri, loadtransient);
    break;
  case WEBKIT_LOAD_COMMITTED:
    setatom(c, AtomUri, uri);
    c->title = uri;
    seturiparameters(c, uri, loadcommitted);
    c->https = webkit_web_view_get_tls_info(c->view, &c->cert, &c->tlserr);
    break;
  case WEBKIT_LOAD_FINISHED:
    seturiparameters(c, uri, loadfinished);
    /* Disabled until we write some WebKitWebExtension for
     * manipulating the DOM directly.
    evalscript(c, "document.documentElement.style.overflow = '%s'",
        enablescrollbars ? "auto" : "hidden");
    */
    runscript(c);
    break;
  }
  updatetitle(c);
}

void mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h,
                        guint modifiers, Client *c) {
  WebKitHitTestResultContext hc = static_cast<WebKitHitTestResultContext>(
      webkit_hit_test_result_get_context(h));

  /* Keep the hit test to know where is the pointer on the next click */
  c->mousepos = h;

  if (hc & OnLink) {
    c->targeturi = webkit_hit_test_result_get_link_uri(h);
  } else if (hc & OnImg) {
    c->targeturi = webkit_hit_test_result_get_image_uri(h);
  } else if (hc & OnMedia) {
    c->targeturi = webkit_hit_test_result_get_media_uri(h);
  } else {
    c->targeturi = NULL;
  }

  c->overtitle = c->targeturi;
  updatetitle(c);
}

gboolean permissionrequested(WebKitWebView *v, WebKitPermissionRequest *r,
                             Client *c) {
  ParamName param = ParameterLast;

  if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(r)) {
    param = Geolocation;
  } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(r)) {
    if (webkit_user_media_permission_is_for_audio_device(
            WEBKIT_USER_MEDIA_PERMISSION_REQUEST(r))) {
      param = AccessMicrophone;
    } else if (webkit_user_media_permission_is_for_video_device(
                   WEBKIT_USER_MEDIA_PERMISSION_REQUEST(r))) {
      param = AccessWebcam;
    }
  } else {
    return FALSE;
  }

  if (curconfig[param].val.i) {
    webkit_permission_request_allow(r);
  } else {
    webkit_permission_request_deny(r);
  }

  return TRUE;
}

gboolean viewusrmsgrcv(WebKitWebView *v, WebKitUserMessage *m,
                       gpointer unused) {
  WebKitUserMessage *r;
  GUnixFDList *gfd;
  char const *name;

  name = webkit_user_message_get_name(m);
  if (strcmp(name, "page-created") != 0) {
    fprintf(stderr, "surf: Unknown UserMessage: %s\n", name);
    return TRUE;
  }

  if (spair[1] < 0) {
    return TRUE;
  }

  gfd = g_unix_fd_list_new_from_array(&spair[1], 1);
  r = webkit_user_message_new_with_fd_list("surf-pipe", NULL, gfd);

  webkit_user_message_send_reply(m, r);

  return TRUE;
}

void webprocessterminated(WebKitWebView *v, WebKitWebProcessTerminationReason r,
                          Client *c) {
  fprintf(stderr, "web process terminated: %s\n",
          r == WEBKIT_WEB_PROCESS_CRASHED ? "crashed" : "no memory");
  closeview(v, c);
}

void setparameter(Client *c, int refresh, ParamName p, Arg const *a) {
  GdkRGBA bgcolor = {0};

  modparams[p] = curconfig[p].prio;

  switch (p) {
  case AccessMicrophone:
    return; /* do nothing */
  case AccessWebcam:
    return; /* do nothing */
  case CaretBrowsing:
    webkit_settings_set_enable_caret_browsing(c->settings, a->i);
    refresh = 0;
    break;
  case Certificate:
    if (a->i) {
      setcert(c, geturi(c));
    }
    return; /* do not update */
  case CookiePolicies:
    webkit_cookie_manager_set_accept_policy(
        webkit_web_context_get_cookie_manager(c->context), cookiepolicy_get());
    refresh = 0;
    break;
  case DarkMode:
    g_object_set(gtk_settings_get_default(),
                 "gtk-application-prefer-dark-theme", a->i, NULL);
    return;
  case DiskCache:
    webkit_web_context_set_cache_model(
        c->context, a->i ? WEBKIT_CACHE_MODEL_WEB_BROWSER
                         : WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
    return; /* do not update */
  case DefaultCharset:
    webkit_settings_set_default_charset(c->settings,
                                        static_cast<gchar const *>(a->v));
    return; /* do not update */
  case DNSPrefetch:
    webkit_settings_set_enable_dns_prefetching(c->settings, a->i);
    return; /* do not update */
  case FileURLsCrossAccess:
    webkit_settings_set_allow_file_access_from_file_urls(c->settings, a->i);
    webkit_settings_set_allow_universal_access_from_file_urls(c->settings,
                                                              a->i);
    return; /* do not update */
  case FontSize:
    webkit_settings_set_default_font_size(c->settings, a->i);
    return; /* do not update */
  case Geolocation:
    refresh = 0;
    break;
  case HideBackground:
    if (a->i) {
      webkit_web_view_set_background_color(c->view, &bgcolor);
    }
    return; /* do not update */
  case Inspector:
    webkit_settings_set_enable_developer_extras(c->settings, a->i);
    return; /* do not update */
  case JavaScript:
    webkit_settings_set_enable_javascript(c->settings, a->i);
    break;
  case KioskMode:
    return; /* do nothing */
  case LoadImages:
    webkit_settings_set_auto_load_images(c->settings, a->i);
    break;
  case MediaManualPlay:
    webkit_settings_set_media_playback_requires_user_gesture(c->settings, a->i);
    break;
  case PreferredLanguages:
    return; /* do nothing */
  case RunInFullscreen:
    return; /* do nothing */
  case ScrollBars:
    /* Disabled until we write some WebKitWebExtension for
     * manipulating the DOM directly.
    enablescrollbars = !enablescrollbars;
    evalscript(c, "document.documentElement.style.overflow = '%s'",
        enablescrollbars ? "auto" : "hidden");
    */
    return; /* do not update */
  case ShowIndicators:
    break;
  case SmoothScrolling:
    webkit_settings_set_enable_smooth_scrolling(c->settings, a->i);
    return; /* do not update */
  case SiteQuirks:
    webkit_settings_set_enable_site_specific_quirks(c->settings, a->i);
    break;
  case SpellChecking:
    webkit_web_context_set_spell_checking_enabled(c->context, a->i);
    return; /* do not update */
  case SpellLanguages:
    return; /* do nothing */
  case StrictTLS:
    webkit_website_data_manager_set_tls_errors_policy(
        webkit_web_view_get_website_data_manager(c->view),
        a->i ? WEBKIT_TLS_ERRORS_POLICY_FAIL : WEBKIT_TLS_ERRORS_POLICY_IGNORE);
    break;
  case Style:
    webkit_user_content_manager_remove_all_style_sheets(
        webkit_web_view_get_user_content_manager(c->view));
    if (a->i) {
      setstyle(c, getstyle(geturi(c)));
    }
    refresh = 0;
    break;
  case WebGL:
    webkit_settings_set_enable_webgl(c->settings, a->i);
    break;
  case ZoomLevel:
    webkit_web_view_set_zoom_level(c->view, a->f);
    return; /* do not update */
  default:
    return; /* do nothing */
  }

  updatetitle(c);
  if (refresh) {
    reload(c, a);
  }
}

void responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c) {
  download(c, webkit_download_get_response(d));
  webkit_download_cancel(d);
}

void clicknewwindow(Client *c, Arg const *a, WebKitHitTestResult *h) {
  Arg arg;

  arg.v = webkit_hit_test_result_get_link_uri(h);
  newwindow(c, &arg, a->i);
}

void clicknavigate(Client *c, Arg const *a, WebKitHitTestResult *h) {
  navigate(c, a);
}

void clickexternplayer(Client *c, Arg const *a, WebKitHitTestResult *h) {
  Arg arg;

  arg = (Arg)VIDEOPLAY(webkit_hit_test_result_get_media_uri(h));
  spawn(c, &arg);
}

void decidenavigation(WebKitPolicyDecision *d, Client *c) {
  WebKitNavigationAction *a =
      webkit_navigation_policy_decision_get_navigation_action(
          WEBKIT_NAVIGATION_POLICY_DECISION(d));

  switch (webkit_navigation_action_get_navigation_type(a)) {
  case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:     /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED:   /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:     /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_RELOAD:           /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_OTHER:            /* fallthrough */
  default:
    /* Do not navigate to links with a "_blank" target (popup) */
    if (webkit_navigation_action_get_frame_name(a)) {
      webkit_policy_decision_ignore(d);
    } else {
      /* Filter out navigation to different domain ? */
      /* get action→urirequest, copy and load in new window+view
       * on Ctrl+Click ? */
      webkit_policy_decision_use(d);
    }
    break;
  }
}

void decidenewwindow(WebKitPolicyDecision *d, Client *c) {
  Arg arg;
  WebKitNavigationAction *a =
      webkit_navigation_policy_decision_get_navigation_action(
          WEBKIT_NAVIGATION_POLICY_DECISION(d));

  switch (webkit_navigation_action_get_navigation_type(a)) {
  case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:   /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:   /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_RELOAD:         /* fallthrough */
  case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
    /* Filter domains here */
    /* If the value of “mouse-button” is not 0, then the navigation was
     * triggered by a mouse event. test for link clicked but no button ? */
    arg.v = webkit_uri_request_get_uri(webkit_navigation_action_get_request(a));
    newwindow(c, &arg, 0);
    break;
  case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
  default:
    break;
  }

  webkit_policy_decision_ignore(d);
}

void decideresource(WebKitPolicyDecision *d, Client *c) {
  int i, isascii = 1;
  WebKitResponsePolicyDecision *r = WEBKIT_RESPONSE_POLICY_DECISION(d);
  WebKitURIResponse *res = webkit_response_policy_decision_get_response(r);
  gchar const *uri = webkit_uri_response_get_uri(res);

  if (g_str_has_suffix(uri, "/favicon.ico")) {
    webkit_policy_decision_ignore(d);
    return;
  }

  if (!g_str_has_prefix(uri, "http://") && !g_str_has_prefix(uri, "https://") &&
      !g_str_has_prefix(uri, "about:") && !g_str_has_prefix(uri, "file://") &&
      !g_str_has_prefix(uri, "webkit://") && !g_str_has_prefix(uri, "data:") &&
      !g_str_has_prefix(uri, "blob:") && strlen(uri) > 0) {
    for (i = 0; i < strlen(uri); i++) {
      if (!g_ascii_isprint(uri[i])) {
        isascii = 0;
        break;
      }
    }
    if (isascii) {
      handleplumb(c, uri);
      webkit_policy_decision_ignore(d);
      return;
    }
  }

  if (webkit_response_policy_decision_is_mime_type_supported(r)) {
    webkit_policy_decision_use(d);
  } else {
    webkit_policy_decision_ignore(d);
    download(c, res);
  }
}

char const *geturi(Client *c) {
  char const *uri;

  if (!(uri = webkit_web_view_get_uri(c->view))) {
    uri = "about:blank";
  }
  return uri;
}

void setatom(Client *c, int a, char const *v) {
  XChangeProperty(dpy, c->xid, atoms[a], atoms[AtomUTF8], 8, PropModeReplace,
                  (unsigned char *)v, strlen(v) + 1);
  XSync(dpy, False);
}

void seturiparameters(Client *c, char const *uri, ParamName *params) {
  Parameter *config, *uriconfig = NULL;
  int i, p;

  for (i = 0; i < LENGTH(uriparams); ++i) {
    if (uriparams[i].uri && !regexec(&(uriparams[i].re), uri, 0, NULL, 0)) {
      uriconfig = uriparams[i].config;
      break;
    }
  }

  curconfig = uriconfig ? uriconfig : defconfig;

  for (i = 0; (p = params[i]) != ParameterLast; ++i) {
    switch (p) {
    default: /* FALLTHROUGH */
      if (!(defconfig[p].prio < curconfig[p].prio ||
            defconfig[p].prio < modparams[p])) {
        continue;
      }
    case Certificate:
    case CookiePolicies:
    case Style:
      setparameter(c, 0, static_cast<ParamName>(p), &curconfig[p].val);
    }
  }
}

void runscript(Client *c) {
  gchar *script;
  gsize l;

  if (g_file_get_contents(scriptfile, &script, &l, NULL) && l) {
    evalscript(c, "%s", script);
  }
  g_free(script);
}

void setcert(Client *c, char const *uri) {
  char const *file = getcert(uri);
  char *host;
  GTlsCertificate *cert;

  if (!file) {
    return;
  }

  if (!(cert = g_tls_certificate_new_from_file(file, NULL))) {
    fprintf(stderr, "Could not read certificate file: %s\n", file);
    return;
  }

  if ((uri = strstr(uri, "https://"))) {
    uri += sizeof("https://") - 1;
    host = g_strndup(uri, strchr(uri, '/') - uri);
    webkit_web_context_allow_tls_certificate_for_host(c->context, cert, host);
    g_free(host);
  }

  g_object_unref(cert);
}

char const *getstyle(char const *uri) {
  int i;

  if (stylefile) {
    return stylefile;
  }

  for (i = 0; i < LENGTH(styles); ++i) {
    if (styles[i].regex && !regexec(&(styles[i].re), uri, 0, NULL, 0)) {
      return styles[i].file;
    }
  }

  return "";
}

void setstyle(Client *c, char const *file) {
  gchar *style;

  if (!g_file_get_contents(file, &style, NULL, NULL)) {
    fprintf(stderr, "Could not read style file: %s\n", file);
    return;
  }

  webkit_user_content_manager_add_style_sheet(
      webkit_web_view_get_user_content_manager(c->view),
      webkit_user_style_sheet_new(style, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
                                  WEBKIT_USER_STYLE_LEVEL_USER, NULL, NULL));

  g_free(style);
}

void download(Client *c, WebKitURIResponse *r) {
  Arg a = (Arg)DOWNLOAD(webkit_uri_response_get_uri(r), geturi(c));
  spawn(c, &a);
}

void newwindow(Client *c, Arg const *a, int noembed) {
  int i = 0;
  char tmp[64];
  char const *cmd[29], *uri;
  Arg const arg = {.v = cmd};

  cmd[i++] = argv0;
  cmd[i++] = "-a";
  cmd[i++] = static_cast<char const *>(curconfig[CookiePolicies].val.v);
  cmd[i++] = curconfig[ScrollBars].val.i ? "-B" : "-b";
  if (cookiefile && g_strcmp0(cookiefile, "")) {
    cmd[i++] = "-c";
    cmd[i++] = cookiefile;
  }
  if (stylefile && g_strcmp0(stylefile, "")) {
    cmd[i++] = "-C";
    cmd[i++] = stylefile;
  }
  cmd[i++] = curconfig[DiskCache].val.i ? "-D" : "-d";
  if (embed && !noembed) {
    cmd[i++] = "-e";
    snprintf(tmp, LENGTH(tmp), "%lu", embed);
    cmd[i++] = tmp;
  }
  cmd[i++] = curconfig[RunInFullscreen].val.i ? "-F" : "-f";
  cmd[i++] = curconfig[Geolocation].val.i ? "-G" : "-g";
  cmd[i++] = curconfig[LoadImages].val.i ? "-I" : "-i";
  cmd[i++] = curconfig[KioskMode].val.i ? "-K" : "-k";
  cmd[i++] = curconfig[Style].val.i ? "-M" : "-m";
  cmd[i++] = curconfig[Inspector].val.i ? "-N" : "-n";
  if (scriptfile && g_strcmp0(scriptfile, "")) {
    cmd[i++] = "-r";
    cmd[i++] = scriptfile;
  }
  cmd[i++] = curconfig[JavaScript].val.i ? "-S" : "-s";
  cmd[i++] = curconfig[StrictTLS].val.i ? "-T" : "-t";
  if (fulluseragent && g_strcmp0(fulluseragent, "")) {
    cmd[i++] = "-u";
    cmd[i++] = fulluseragent;
  }
  if (showxid) {
    cmd[i++] = "-w";
  }
  cmd[i++] = curconfig[Certificate].val.i ? "-X" : "-x";
  /* do not keep zoom level */
  cmd[i++] = "--";
  if ((uri = static_cast<char const *>(a->v))) {
    cmd[i++] = uri;
  }
  cmd[i] = NULL;

  spawn(c, &arg);
}

void navigate(Client *c, Arg const *a) {
  if (a->i < 0) {
    webkit_web_view_go_back(c->view);
  } else if (a->i > 0) {
    webkit_web_view_go_forward(c->view);
  }
}

void spawn(Client *c, Arg const *a) {
  if (fork() == 0) {
    if (dpy) {
      close(ConnectionNumber(dpy));
    }
    close(spair[0]);
    close(spair[1]);
    setsid();
    execvp(((char **)a->v)[0], (char **)a->v);
    fprintf(stderr, "%s: execvp %s", argv0, ((char **)a->v)[0]);
    perror(" failed");
    exit(1);
  }
}

void handleplumb(Client *c, char const *uri) {
  Arg a = (Arg)PLUMB(uri);
  spawn(c, &a);
}

void evalscript(Client *c, char const *jsstr, ...) {
  va_list ap;
  gchar *script;

  va_start(ap, jsstr);
  script = g_strdup_vprintf(jsstr, ap);
  va_end(ap);

  webkit_web_view_evaluate_javascript(c->view, script, -1, NULL, NULL, NULL,
                                      NULL, NULL);
  g_free(script);
}

char const *getcert(char const *uri) {
  int i;

  for (i = 0; i < LENGTH(certs); ++i) {
    if (certs[i].regex && !regexec(&(certs[i].re), uri, 0, NULL, 0)) {
      return certs[i].file;
    }
  }

  return NULL;
}

void usage(void) {
  die("usage: surf [-bBdDfFgGiIkKmMnNsStTvwxX]\n"
      "[-a cookiepolicies ] [-c cookiefile] [-C stylefile] [-e xid]\n"
      "[-r scriptfile] [-u useragent] [-z zoomlevel] [uri]\n");
}

void zoom(Client *c, Arg const *a) {
  if (a->i > 0) {
    webkit_web_view_set_zoom_level(c->view, curconfig[ZoomLevel].val.f + 0.1);
  } else if (a->i < 0) {
    webkit_web_view_set_zoom_level(c->view, curconfig[ZoomLevel].val.f - 0.1);
  } else {
    webkit_web_view_set_zoom_level(c->view, 1.0);
  }

  curconfig[ZoomLevel].val.f = webkit_web_view_get_zoom_level(c->view);
}
